package io.adminboot.system.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public class RedisClient implements IRedisClient {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Collection<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    @Override
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    @Override
    public void delete(Collection<String> key) {
        redisTemplate.delete(key);
    }

    @Override
    public <T> T get(String key, Class<T> clazz) {
        return (T) redisTemplate.opsForValue().get(key);
    }

    @Override
    public <T> List<T> mget(Collection<String> keys, Class<T> clazz) {
        return (List<T>) redisTemplate.opsForValue().multiGet(keys);
    }

    @Override
    public void set(String key, Object obj, long timeout, TimeUnit unit) {
        if (obj == null) {
            return;
        }
        if (timeout > 0) {
            redisTemplate.opsForValue().set(key, obj, timeout, unit);
        } else {
            redisTemplate.opsForValue().set(key, obj);
        }
    }

    @Override
    public <T> T getAndSet(String key, Object obj, Class<T> clazz) {
        if (obj == null) {
            return get(key, clazz);
        }

        return (T) redisTemplate.opsForValue().getAndSet(key, obj);
    }

    @Override
    public int decrement(String key, int delta) {
        Long value = redisTemplate.opsForValue().increment(key, -delta);
        return value.intValue();
    }

    @Override
    public int increment(String key, int delta) {
        Long value = redisTemplate.opsForValue().increment(key, delta);
        return value.intValue();
    }

    @Override
    public int size(String key) {
        return redisTemplate.opsForList().size(key).intValue();
    }

    @Override
    public <T> List<T> range(String key, long start, long end, Class<T> clazz) {
        return (List<T>) redisTemplate.opsForList().range(key, start, end);
    }

    @Override
    public void rightPushAll(String key, Collection<?> values, Long timeout,
                             TimeUnit unit) {
        if (values == null || values.isEmpty()) {
            return;
        }

        redisTemplate.opsForList().rightPushAll(key, values);
        if (timeout != null) {
            redisTemplate.expire(key, timeout, unit);
        }
    }

    @Override
    public <T> void leftPush(String key, T obj) {
        if (obj == null) {
            return;
        }

        redisTemplate.opsForList().leftPush(key, obj);
    }

    @Override
    public <T> T leftPop(String key, Class<T> clazz) {
        return (T) redisTemplate.opsForList().leftPop(key);
    }

    @Override
    public Long queueIn(String key, Object obj) {
        return redisTemplate.opsForList().rightPush(key, obj);
    }

    @Override
    public <T> T queueOut(String key, Class<T> clazz) {
        return (T) redisTemplate.opsForList().leftPop(key);
    }

    @Override
    public Long queueLength(String key) {
        return redisTemplate.opsForList().size(key);
    }

    @Override
    public void remove(String key, int count, Object obj) {
        if (obj == null) {
            return;
        }

        redisTemplate.opsForList().remove(key, count, obj);
    }

    /* ----------- zset --------- */
    @Override
    public int zcard(String key) {
        return redisTemplate.opsForZSet().zCard(key).intValue();
    }

    @Override
    public <T> List<T> zrange(String key, long start, long end, Class<T> clazz) {
        Set<T> set = (Set<T>) redisTemplate.opsForZSet().range(key, start, end);
        return setToList(set);
    }

    private <T> List<T> setToList(Set<T> set) {
        if (set == null) {
            return null;
        }
        return new ArrayList<T>(set);
    }

    public void zadd(String key, Object obj, double score) {
        if (obj == null) {
            return;
        }
        redisTemplate.opsForZSet().add(key, obj, score);
    }

    @Override
    public void zaddAll(String key, List<ZSetOperations.TypedTuple<?>> tupleList, Long timeout, TimeUnit unit) {
        if (tupleList == null || tupleList.isEmpty()) {
            return;
        }

        Set<ZSetOperations.TypedTuple<Object>> tupleSet = toTupleSet(tupleList);
        redisTemplate.opsForZSet().add(key, tupleSet);
        if (timeout != null) {
            redisTemplate.expire(key, timeout, unit);
        }
    }

    private Set<ZSetOperations.TypedTuple<Object>> toTupleSet(List<ZSetOperations.TypedTuple<?>> tupleList) {
        Set<ZSetOperations.TypedTuple<Object>> tupleSet = new LinkedHashSet<>();
        for (ZSetOperations.TypedTuple<?> t : tupleList) {
            tupleSet.add(new DefaultTypedTuple<>(t.getValue(), t.getScore()));
        }
        return tupleSet;
    }

    @Override
    public Long zrem(String key, Object obj) {
        if (obj == null) {
            return new Long(0);
        }
        return redisTemplate.opsForZSet().remove(key, obj);
    }

    @Override
    public void unionStore(String destKey, Collection<String> keys, Long timeout, TimeUnit unit) {
        if (keys == null || keys.isEmpty()) {
            return;
        }

        Object[] keyArr = keys.toArray();
        String key = (String) keyArr[0];

        Collection<String> otherKeys = new ArrayList<>(keys.size() - 1);
        for (int i = 1; i < keyArr.length; i++) {
            otherKeys.add((String) keyArr[i]);
        }

        redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
        if (timeout != null) {
            redisTemplate.expire(destKey, timeout, unit);
        }
    }

}
